package de.e2it3t.schulverwaltung.data;

import java.lang.reflect.Field;
import java.util.List;
import javax.swing.table.AbstractTableModel;

/**
 * Generelles Modell für JTable. <br>
 * <br>
 * Um Daten in einer Tabelle anzeigen zu können, muss man die Daten in eine
 * erforderliche Form bringen. Die Daten extra zu transformieren ist
 * umständlich, da die Daten bereit in Objektform vorliegen (wenn man z.B. die
 * Datenbank anfragt). Somit muss man eine Tabellen-Modell implementieren. Da
 * sich dies wiederum immer wieder ähnelt, bildet diese Klasse die Basis.<br>
 * <br>
 * Es wird eine Liste mit Objekten erwartet, dessen Felder per Reflektion
 * angesteuert werden.<br>
 * <br>
 * <b>Hinweis</b>:<br>
 * <br>
 * Um die Implementierung dieser Klasse zu verstehen, muss man die Konzepte der
 * Reflection verstehen. Es werden direkt private Attribute von Klassen
 * angefragt. Zur Laufzeit kennen wir nur die Namen der Attribute. Reflection
 * ermöglicht einen dynamischen Zugriff ohne Feste Klassen im Code zu kodieren.
 * Das verletzt das Prinzip der Kapselung, sorgt aber widerum für mehr Komfort.
 * 
 * @author Viktor Reiser
 */
public class CommonTableModel extends AbstractTableModel {
    
    private List<?> data;
    private String [] fieldNames;
    private String [] columnNames;
    private Field [] fields;
    
    /**
     * Modell erstellen.
     * 
     * @param data Listen mit den Daten-Objekten (z.B. {@link Schueler}
     * @param fieldNames Feldnamen (Attribute) welche ausgelesen werden sollen -
     * die Reihenfolge entspricht den Spalten in der Tabelle
     * @param columnNames visuelle Namen der Spalten (Header)
     */
    public CommonTableModel(List<?> data, String [] fieldNames, String [] columnNames) {
        if (fieldNames.length != columnNames.length) {
            throw new IllegalArgumentException(
                    "columns.length != columnNames.length");
        }
        
        this.data = data;
        this.fieldNames = fieldNames;
        this.columnNames = columnNames;
        this.fields = new Field [fieldNames.length];
        
        // wir können nur die Attribute der Klasse setzen,
        // wenn Daten gegeben sind
        if (!data.isEmpty()) {
           setupFields();
        }
    }
    
    private void setupFields() {
        // wir suchen alle Attribute der Klasse
        // die Klasse erhalten wir aus dem ersten Datensatz
        Class<?> clazz = data.get(0).getClass();
        
        for (int i = 0; i < fieldNames.length; i++) {
            try {
                fields[i] = getField(clazz, fieldNames[i]);
                fields[i].setAccessible(true);
            } catch (Exception ex) {
                throw new RuntimeException(
                        clazz.getName() + " has no field " + fieldNames[i], ex);
            }
        } 
    }
    
    private Field getField(Class<?> clazz, String name) {
        if (clazz == null || clazz == Object.class) {
            throw new RuntimeException();
        }
        
        try {
            return clazz.getDeclaredField(name);
        } catch (Exception e) {
            return getField(clazz.getSuperclass(), name);
        }
    }
    

    @Override
    public int getRowCount() {
        return data.size();
    }

    @Override
    public int getColumnCount() {
        return fieldNames.length;
    }

    @Override
    public Object getValueAt(int row, int col) {
        try {
            // nimm den Wert des Attributs per Reflaction
            return fields[col].get(data.get(row));
        } catch (Exception ex) {
            throw new RuntimeException(
                    "Failed to get field value " + fieldNames[col]
                    + " from object with class "
                    + data.get(row).getClass().getName(), ex);
        }
    }

    @Override
    public boolean isCellEditable(int row, int col) {
        return false;
    }

    @Override
    public String getColumnName(int col) {
        return columnNames[col];
    }
}
